package jmat.scarider

import dispatch._
import dispatch.google.cl.AuthRequest
import dispatch.google.cl.ClientLogin._
import java.util.Date
import javax.xml.datatype.XMLGregorianCalendar
import javax.xml.datatype.DatatypeFactory
import java.io.File
import java.util.Properties
import java.io.InputStream
import scala.xml.NodeSeq
import scala.collection.immutable.HashSet

object toDateFromXml {
	def apply(textRepresentation : String) : Date = {
		val factory = DatatypeFactory.newInstance()
		return factory.newXMLGregorianCalendar(textRepresentation).toGregorianCalendar().getTime()
	}
}

case class Entry(val id : String,
				 val title : String,
				 val summary : String,
				 val link : String,
				 val categories : Seq[String],
				 val published : Date,
				 val updated : Date,
				 val source: String)

case class EntriesList(val entries : Seq[Entry], val continuationMark : Option[String]) extends Traversable[Entry]() {
  override def foreach[U] (f: Entry => U) = entries.foreach[U](f)
}

class Greader(config : InputStream) {
	require(config != null)
	val configProperties = new Properties()
	configProperties.load(config)
	val username = configProperties.getProperty(Greader.CONFIG_USERNAME_KEY)
	val password = configProperties.getProperty(Greader.CONFIG_PASSWORD_KEY)
	require(username != null)
	require(password != null)
	
	var hostname = "www.google.com"
	
	def getUnreadCount() = {
	  var params = Map("client" -> Greader.CLIENT_ID)
	  val objects = httpRequest(/("api") / "0" / "unread-count" <<? params, { _ <> { _ \\ "list" \\ "object" } })
	  for (counter <- objects find { obj => (obj \ "string" text).contains(Greader.READING_CATEGORY) };
		   count <- counter \ "number" find { obj => (obj \ "@name" text).contains("count") })
		  		yield (count text) toInt
	}
	
	def getReadingList(excludeReaded : Boolean = true, 
					   previousList : EntriesList = EntriesList(Nil, None)) : EntriesList = {
	  var params = Map("client" -> Greader.CLIENT_ID)
	  if (excludeReaded)
		  params += "xt" -> EntryState.READ.toString()
	  if (previousList.continuationMark.isDefined)
		  params += "c" -> previousList.continuationMark.get
	  val res = httpRequest(/("atom") / "user" / "-" / "state" / "com.google" / "reading-list" <<? params,
    		  				{ _ <> { _ \\ "feed" } })
      
      val entries = res \ "entry" map { entry =>
	        Entry(entry \ "id" text, 
	        	  entry \ "title" text,
	        	  entry \ "summary" text,
	        	  entry \ "link" \ "@href" text,
	        	  entry \ "category" map { _ \ "@label" text },
	        	  toDateFromXml(entry \ "published" text),
	        	  toDateFromXml(entry \ "updated" text),
	        	  entry \ "source" \ "@gr:stream-id" text)
	  }
	  EntriesList(entries, Some(res \ "continuation" text))
	}
	
	def markAsReaded(entry : Entry) = {
	  require(!entry.categories.contains(Greader.READ_CATEGORY))
	  changeEntryState(entry, Some(EntryState.KEPT_UNREAD), Some(EntryState.READ))
	}
	
	def unmarkAsReaded(entry : Entry) = {
	  require(entry.categories.contains(Greader.READ_CATEGORY))
	  changeEntryState(entry, Some(EntryState.READ), Some(EntryState.KEPT_UNREAD))
	}
	
	def markAsStarred(entry : Entry) = {
	  require(!entry.categories.contains(Greader.STAR_CATEGORY))
	  changeEntryState(entry, None, Some(EntryState.STAR))
	}
	
	def unmarkAsStarred(entry : Entry) = {
	  require(entry.categories.contains(Greader.STAR_CATEGORY))
	  changeEntryState(entry, Some(EntryState.STAR), None)
	}
															  
	private def changeEntryState(entry : Entry, previousState : Option[EntryState.Value], nextState : Option[EntryState.Value]) = {
		var params = Map("i" -> entry.id, "ac" -> "edit", "T" -> apiToken)
		if (previousState.isDefined)
			params += ("r" -> previousState.get.toString())
		if (nextState.isDefined)
			params += ("a" -> nextState.get.toString())

		val result = httpRequest(/("api") / "0" / "edit-tag" <<? Map("client" -> Greader.CLIENT_ID) << params, { _ as_str })
		require(result == "OK")
	}
	
	private lazy val authToken = {
		val http = new Http
		val auth_req = AuthRequest("reader", username, password)
		http(auth_req authorize)
	}
	
	private lazy val apiToken = httpRequest(/("api") / "0" / "token", { _ as_str })
	
	private def httpRequest[T](request : dispatch.Request, 
							   handlerAdder : dispatch.Request => dispatch.Handler[T]) = {
		val http = new Http
		http(handlerAdder(:/(hostname) / "reader" <& request <@ authToken))
	}
}

object Greader {
	val CONFIG_USERNAME_KEY = "email"
	val CONFIG_PASSWORD_KEY = "password"
	val READ_CATEGORY = "read"
	val READING_CATEGORY = "reading-list"
	val STAR_CATEGORY = "starred"
	val CLIENT_ID = "contact:juanmi.alvarez@gmail.com" 
}

object EntryState extends Enumeration {
  type EntryState = Value
  val STAR = Value("user/-/state/com.google/starred")
  val READ = Value("user/-/state/com.google/read")
  val KEPT_UNREAD = Value("user/-/state/com.google/kept-unread")
}